Skip to content

Focus a text input as soon as it appears - #85

Open
levi730 wants to merge 1 commit into
NativePHP:mainfrom
levi730:feat/text-input-autofocus
Open

Focus a text input as soon as it appears#85
levi730 wants to merge 1 commit into
NativePHP:mainfrom
levi730:feat/text-input-autofocus

Conversation

@levi730

@levi730 levi730 commented Sep 5, 2026

Copy link
Copy Markdown

What

Adds autofocus to the text inputs, so a field can take focus and raise the keyboard as soon as it appears.

<native:outlined-text-input label="Signer Name" native:model="signerName" autofocus />

Blade autofocus / auto-focus, fluent ->autofocus(). Opt-in, so no existing screen changes behaviour.

Why

A screen whose whole purpose is one field shouldn't also ask the user to tap it.

This came out of a driver app with a two-step signature flow — draw a signature, then name the signer. The second step exists only to collect a name, and arriving there with the keyboard down reads as a dead end. keep-focus-on-submit covers "stay focused after return", but there's currently nothing for "start focused".

How

The focus state was already there on both platforms; nothing exposed it.

  • iOSNativeUITextInputCore already holds a @FocusState bound to the field for keep-focus-on-submit. This adds an .onAppear path to it, deferred a runloop because @FocusState doesn't take while the view is still being installed. It also skips disabled / read-only fields, which can't hold first responder anyway.
  • Android — no requester existed, so Modifier.nuiAutofocus() is added alongside nuiA11y in TextInputShared.kt and applied by all three renderers.

Both fire on first appearance only. Android's LaunchedEffect is keyed on Unit rather than on the flag, so a recomposition can't steal focus back from wherever the user has since moved it.

Notes

  • Only one field per screen should set it — two would race for first responder and the loser's keyboard flickers. That's called out in the docblock; it didn't seem worth enforcing in code.
  • A falsy attribute is ignored rather than treated as opt-in, so autofocus="{{ $condition }}" behaves when the condition is false.

Tests

tests/BaseTextInputAutofocusTest.php — 12 cases across all three variants: absent by default, both attribute spellings, falsy values ignored, and the fluent setter both ways. Verified failing without the source change (8 fail), and pint --test is clean.

The renderer changes are Swift and Kotlin, so they're not covered by the PHP suite. I've run the iOS side on device; the Android path is written but untested on a device — worth a look from someone with an emulator handy.

A screen whose whole purpose is one field should not also ask the user to tap
it. The pattern this comes from is a two-step flow — draw a signature, then
name the signer — where the second step exists only to collect a name, and
arriving there with the keyboard down reads as a dead end.

The focus state was already there on both platforms; nothing exposed it. iOS
holds a @focusstate bound to the field for `keep-focus-on-submit`, and this
adds an .onAppear path to it. Android had no requester, so one is added as a
Modifier extension shared by all three renderers.

Both fire on first appearance only. iOS defers a runloop because @focusstate
does not take while the view is still being installed; Android keys its
LaunchedEffect on Unit rather than on the flag, so a recomposition cannot
steal focus back from wherever the user has since moved it. iOS also skips
disabled and read-only fields, which cannot hold first responder anyway.

Blade `autofocus` / `auto-focus`, fluent `->autofocus()`. Opt-in, so no
existing screen changes behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant